using System;
using System.Collections.Generic;
using System.Text;
using Hack.Properties;


namespace Hack.DataStructures
{
    [Serializable]
    public struct Interval<T> : IEquatable<Interval<T>>, IComparable<Interval<T>> 
        where T:struct, IComparable<T>, IEquatable<T>
    {
        public readonly T Min;
        public readonly T Max;

        public Interval(T min, T max)
        {
            if (min.CompareTo(max) > 0)
                throw new ArgumentException(Resources.MinIsGreaterThanMax);

            this.Min = min;
            this.Max = max;
        }

        public bool Contains(T value)
        {
            return Min.CompareTo(value) <= 0 && value.CompareTo(Max) < 0;
        }

        public bool Overlap(Interval<T> other)
        {
            if (Max.CompareTo(other.Min)<=0)
                return false;
            if (other.Max.CompareTo(Min) <= 0)
                return false;

            return true;
        }


        public Interval<T>? Intersection(Interval<T> other)
        {
            T minVal = Min.CompareTo(other.Min) > 0 ? Min : other.Min;
            T maxVal = Max.CompareTo(other.Max) < 0 ? Max : other.Max;

            if (minVal.CompareTo(maxVal) >= 0)
                return null;

            return new Interval<T>(minVal, maxVal);
        }

        public Interval<T>? Union(Interval<T> other)
        {
            if (!this.Overlap(other))
                return null;

            T minVal = Min.CompareTo(other.Min) > 0 ? other.Min : Min;
            T maxVal = Max.CompareTo(other.Max) < 0 ? other.Max : Max;

            return new Interval<T>(minVal, maxVal);
        }

        public bool Subset(Interval<T> other)
        {
            return this.Min.CompareTo(other.Min) <= 0 && other.Max.CompareTo(this.Max) <= 0;
        }


        public IEnumerable<T> Elements()
        {
            yield return Min;
            yield return Max;
        }

        public int CompareTo(Interval<T> other)
        {
            return Min.CompareTo(other.Min);
        }


        public override string ToString()
        {
            return "[" + Min + " - " + Max + "]"; 
        }        

        public bool Equals(Interval<T> other)
        {
            return other.Min.Equals(Min) && other.Max.Equals(Max);
        }

        public override bool Equals(object obj)
        {
            if (obj == null || !(obj is Interval<T>))
                return false;

            return Equals((Interval<T>)obj); 
        }

        public override int GetHashCode()
        {
            return Min.GetHashCode();
        }
    }

    public static class IntervalExtensions
    {
        public static int Distance(this Interval<int> interval, int point)
        {
            return point < interval.Min ? interval.Min - point :
                    point > interval.Max ? point - interval.Max : 0;
        }

        public static long Distance(this Interval<long> interval, long point)
        {
            return point < interval.Min ? interval.Min - point :
                    point > interval.Max ? point - interval.Max : 0;
        }

        public static double Distance(this Interval<double> interval, double point)
        {
            return point < interval.Min ? interval.Min - point :
                    point > interval.Max ? point - interval.Max : 0;
        }

        public static float Distance(this Interval<float> interval, float point)
        {
            return point < interval.Min ? interval.Min - point :
                    point > interval.Max ? point - interval.Max : 0;
        }

        public static decimal Distance(this Interval<decimal> interval, decimal point)
        {
            return point < interval.Min ? interval.Min - point :
                    point > interval.Max ? point - interval.Max : 0;
        }

        public static TimeSpan Distance(this Interval<DateTime> interval, DateTime point)
        {
            return point < interval.Min ? interval.Min - point :
                    point > interval.Max ? point - interval.Max : new TimeSpan(0);
        }
    }
}
